// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/child_process_security_policy_impl.h"

#include <algorithm>
#include <utility>

#include "base/command_line.h"
#include "base/debug/dump_without_crashing.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "build/build_config.h"
#include "content/browser/site_instance_impl.h"
#include "content/common/site_isolation_policy.h"
#include "content/public/browser/child_process_data.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/bindings_policy.h"
#include "content/public/common/url_constants.h"
#include "net/base/filename_util.h"
#include "net/url_request/url_request.h"
#include "storage/browser/fileapi/file_permission_policy.h"
#include "storage/browser/fileapi/file_system_url.h"
#include "storage/browser/fileapi/isolated_context.h"
#include "storage/common/fileapi/file_system_util.h"
#include "url/gurl.h"

namespace content {

namespace {

    // Used internally only. These bit positions have no relationship to any
    // underlying OS and can be changed to accommodate finer-grained permissions.
    enum ChildProcessSecurityPermissions {
        READ_FILE_PERMISSION = 1 << 0,
        WRITE_FILE_PERMISSION = 1 << 1,
        CREATE_NEW_FILE_PERMISSION = 1 << 2,
        CREATE_OVERWRITE_FILE_PERMISSION = 1 << 3,
        DELETE_FILE_PERMISSION = 1 << 4,

        // Used by Media Galleries API
        COPY_INTO_FILE_PERMISSION = 1 << 5,
    };

    // Used internally only. Bitmasks that are actually used by the Grant* and Can*
    // methods. These contain one or more ChildProcessSecurityPermissions.
    enum ChildProcessSecurityGrants {
        READ_FILE_GRANT = READ_FILE_PERMISSION,
        WRITE_FILE_GRANT = WRITE_FILE_PERMISSION,

        CREATE_NEW_FILE_GRANT = CREATE_NEW_FILE_PERMISSION | COPY_INTO_FILE_PERMISSION,

        CREATE_READ_WRITE_FILE_GRANT = CREATE_NEW_FILE_PERMISSION | CREATE_OVERWRITE_FILE_PERMISSION | READ_FILE_PERMISSION | WRITE_FILE_PERMISSION | COPY_INTO_FILE_PERMISSION | DELETE_FILE_PERMISSION,

        COPY_INTO_FILE_GRANT = COPY_INTO_FILE_PERMISSION,
        DELETE_FILE_GRANT = DELETE_FILE_PERMISSION,
    };

    // https://crbug.com/646278 Valid blob URLs should contain canonically
    // serialized origins.
    bool IsMalformedBlobUrl(const GURL& url)
    {
        if (!url.SchemeIsBlob())
            return false;

        // If the part after blob: survives a roundtrip through url::Origin, then
        // it's a normal blob URL.
        std::string canonical_origin = url::Origin(url).Serialize();
        canonical_origin.append(1, '/');
        if (base::StartsWith(url.GetContent(), canonical_origin,
                base::CompareCase::INSENSITIVE_ASCII))
            return false;

        // blob:blobinternal:// is used by blink for stream URLs. This doesn't survive
        // url::Origin canonicalization -- blobinternal is a fake scheme -- but allow
        // it anyway. TODO(nick): Added speculatively, might be unnecessary.
        if (base::StartsWith(url.GetContent(), "blobinternal://",
                base::CompareCase::INSENSITIVE_ASCII))
            return false;

        // This is a malformed blob URL.
        return true;
    }

} // namespace

// The SecurityState class is used to maintain per-child process security state
// information.
class ChildProcessSecurityPolicyImpl::SecurityState {
public:
    SecurityState()
        : enabled_bindings_(0)
        , can_read_raw_cookies_(false)
        , can_send_midi_sysex_(false)
    {
    }

    ~SecurityState()
    {
        scheme_policy_.clear();
        storage::IsolatedContext* isolated_context = storage::IsolatedContext::GetInstance();
        for (FileSystemMap::iterator iter = filesystem_permissions_.begin();
             iter != filesystem_permissions_.end();
             ++iter) {
            isolated_context->RemoveReference(iter->first);
        }
        UMA_HISTOGRAM_COUNTS("ChildProcessSecurityPolicy.PerChildFilePermissions",
            file_permissions_.size());
    }

    // Grant permission to request URLs with the specified origin.
    void GrantOrigin(const url::Origin& origin)
    {
        origin_set_.insert(origin);
    }

    // Grant permission to request URLs with the specified scheme.
    void GrantScheme(const std::string& scheme)
    {
        scheme_policy_[scheme] = true;
    }

    // Revoke permission to request URLs with the specified scheme.
    void RevokeScheme(const std::string& scheme)
    {
        scheme_policy_[scheme] = false;
    }

    // Grant certain permissions to a file.
    void GrantPermissionsForFile(const base::FilePath& file, int permissions)
    {
        base::FilePath stripped = file.StripTrailingSeparators();
        file_permissions_[stripped] |= permissions;
        UMA_HISTOGRAM_COUNTS("ChildProcessSecurityPolicy.FilePermissionPathLength",
            stripped.value().size());
    }

    // Grant navigation to a file but not the file:// scheme in general.
    void GrantRequestOfSpecificFile(const base::FilePath& file)
    {
        request_file_set_.insert(file.StripTrailingSeparators());
    }

    // Revokes all permissions granted to a file.
    void RevokeAllPermissionsForFile(const base::FilePath& file)
    {
        base::FilePath stripped = file.StripTrailingSeparators();
        file_permissions_.erase(stripped);
        request_file_set_.erase(stripped);
    }

    // Grant certain permissions to a file.
    void GrantPermissionsForFileSystem(const std::string& filesystem_id,
        int permissions)
    {
        if (!base::ContainsKey(filesystem_permissions_, filesystem_id))
            storage::IsolatedContext::GetInstance()->AddReference(filesystem_id);
        filesystem_permissions_[filesystem_id] |= permissions;
    }

    bool HasPermissionsForFileSystem(const std::string& filesystem_id,
        int permissions)
    {
        FileSystemMap::const_iterator it = filesystem_permissions_.find(filesystem_id);
        if (it == filesystem_permissions_.end())
            return false;
        return (it->second & permissions) == permissions;
    }

#if defined(OS_ANDROID)
    // Determine if the certain permissions have been granted to a content URI.
    bool HasPermissionsForContentUri(const base::FilePath& file,
        int permissions)
    {
        DCHECK(!file.empty());
        DCHECK(file.IsContentUri());
        if (!permissions)
            return false;
        base::FilePath file_path = file.StripTrailingSeparators();
        FileMap::const_iterator it = file_permissions_.find(file_path);
        if (it != file_permissions_.end())
            return (it->second & permissions) == permissions;
        return false;
    }
#endif

    void GrantBindings(int bindings)
    {
        enabled_bindings_ |= bindings;
    }

    void GrantReadRawCookies()
    {
        can_read_raw_cookies_ = true;
    }

    void RevokeReadRawCookies()
    {
        can_read_raw_cookies_ = false;
    }

    void GrantPermissionForMidiSysEx()
    {
        can_send_midi_sysex_ = true;
    }

    bool CanCommitOrigin(const url::Origin& origin)
    {
        return base::ContainsKey(origin_set_, origin);
    }

    // Determine whether permission has been granted to commit |url|.
    bool CanCommitURL(const GURL& url)
    {
        DCHECK(!url.SchemeIsBlob() && !url.SchemeIsFileSystem())
            << "inner_url extraction should be done already.";
        // Having permission to a scheme implies permission to all of its URLs.
        SchemeMap::const_iterator scheme_judgment(
            scheme_policy_.find(url.scheme()));
        if (scheme_judgment != scheme_policy_.end())
            return scheme_judgment->second;

        // Otherwise, check for permission for specific origin.
        if (CanCommitOrigin(url::Origin(url)))
            return true;

        // file:// URLs are more granular.  The child may have been given
        // permission to a specific file but not the file:// scheme in general.
        if (url.SchemeIs(url::kFileScheme)) {
            base::FilePath path;
            if (net::FileURLToFilePath(url, &path))
                return base::ContainsKey(request_file_set_, path);
        }

        return false; // Unmentioned schemes are disallowed.
    }

    // Determine if the certain permissions have been granted to a file.
    bool HasPermissionsForFile(const base::FilePath& file, int permissions)
    {
#if defined(OS_ANDROID)
        if (file.IsContentUri())
            return HasPermissionsForContentUri(file, permissions);
#endif
        if (!permissions || file.empty() || !file.IsAbsolute())
            return false;
        base::FilePath current_path = file.StripTrailingSeparators();
        base::FilePath last_path;
        int skip = 0;
        while (current_path != last_path) {
            base::FilePath base_name = current_path.BaseName();
            if (base_name.value() == base::FilePath::kParentDirectory) {
                ++skip;
            } else if (skip > 0) {
                if (base_name.value() != base::FilePath::kCurrentDirectory)
                    --skip;
            } else {
                FileMap::const_iterator it = file_permissions_.find(current_path);
                if (it != file_permissions_.end())
                    return (it->second & permissions) == permissions;
            }
            last_path = current_path;
            current_path = current_path.DirName();
        }

        return false;
    }

    bool CanAccessDataForOrigin(const GURL& gurl)
    {
        if (origin_lock_.is_empty())
            return true;
        // TODO(creis): We must pass the valid browser_context to convert hosted
        // apps URLs.  Currently, hosted apps cannot set cookies in this mode.
        // See http://crbug.com/160576.
        GURL site_gurl = SiteInstanceImpl::GetSiteForURL(NULL, gurl);
        return origin_lock_ == site_gurl;
    }

    void LockToOrigin(const GURL& gurl)
    {
        origin_lock_ = gurl;
    }

    bool has_web_ui_bindings() const
    {
        return enabled_bindings_ & BINDINGS_POLICY_WEB_UI;
    }

    bool can_read_raw_cookies() const
    {
        return can_read_raw_cookies_;
    }

    bool can_send_midi_sysex() const
    {
        return can_send_midi_sysex_;
    }

private:
    typedef std::map<std::string, bool> SchemeMap;
    typedef std::set<url::Origin> OriginSet;

    typedef int FilePermissionFlags; // bit-set of base::File::Flags
    typedef std::map<base::FilePath, FilePermissionFlags> FileMap;
    typedef std::map<std::string, FilePermissionFlags> FileSystemMap;
    typedef std::set<base::FilePath> FileSet;

    // Maps URL schemes to whether permission has been granted or revoked:
    //   |true| means the scheme has been granted.
    //   |false| means the scheme has been revoked.
    // If a scheme is not present in the map, then it has never been granted
    // or revoked.
    SchemeMap scheme_policy_;

    // The set of URL origins to which the child process has been granted
    // permission.
    OriginSet origin_set_;

    // The set of files the child process is permited to upload to the web.
    FileMap file_permissions_;

    // The set of files the child process is permitted to load.
    FileSet request_file_set_;

    int enabled_bindings_;

    bool can_read_raw_cookies_;

    bool can_send_midi_sysex_;

    GURL origin_lock_;

    // The set of isolated filesystems the child process is permitted to access.
    FileSystemMap filesystem_permissions_;

    DISALLOW_COPY_AND_ASSIGN(SecurityState);
};

ChildProcessSecurityPolicyImpl::ChildProcessSecurityPolicyImpl()
{
    // We know about these schemes and believe them to be safe.
    RegisterWebSafeScheme(url::kHttpScheme);
    RegisterWebSafeScheme(url::kHttpsScheme);
    RegisterWebSafeScheme(url::kFtpScheme);
    RegisterWebSafeScheme(url::kDataScheme);
    RegisterWebSafeScheme("feed");

    // TODO(nick): https://crbug.com/651534 blob: and filesystem: schemes embed
    // other origins, so we should not treat them as web safe. Remove callers of
    // IsWebSafeScheme(), and then eliminate the next two lines.
    RegisterWebSafeScheme(url::kBlobScheme);
    RegisterWebSafeScheme(url::kFileSystemScheme);

    // We know about the following pseudo schemes and treat them specially.
    RegisterPseudoScheme(url::kAboutScheme);
    RegisterPseudoScheme(url::kJavaScriptScheme);
    RegisterPseudoScheme(kViewSourceScheme);
    RegisterPseudoScheme(url::kHttpSuboriginScheme);
    RegisterPseudoScheme(url::kHttpsSuboriginScheme);
}

ChildProcessSecurityPolicyImpl::~ChildProcessSecurityPolicyImpl()
{
}

// static
ChildProcessSecurityPolicy* ChildProcessSecurityPolicy::GetInstance()
{
    return ChildProcessSecurityPolicyImpl::GetInstance();
}

ChildProcessSecurityPolicyImpl* ChildProcessSecurityPolicyImpl::GetInstance()
{
    return base::Singleton<ChildProcessSecurityPolicyImpl>::get();
}

void ChildProcessSecurityPolicyImpl::Add(int child_id)
{
    base::AutoLock lock(lock_);
    AddChild(child_id);
}

void ChildProcessSecurityPolicyImpl::AddWorker(int child_id,
    int main_render_process_id)
{
    base::AutoLock lock(lock_);
    AddChild(child_id);
    worker_map_[child_id] = main_render_process_id;
}

void ChildProcessSecurityPolicyImpl::Remove(int child_id)
{
    base::AutoLock lock(lock_);
    security_state_.erase(child_id);
    worker_map_.erase(child_id);
}

void ChildProcessSecurityPolicyImpl::RegisterWebSafeScheme(
    const std::string& scheme)
{
    base::AutoLock lock(lock_);
    DCHECK_EQ(0U, schemes_okay_to_request_in_any_process_.count(scheme))
        << "Add schemes at most once.";
    DCHECK_EQ(0U, pseudo_schemes_.count(scheme))
        << "Web-safe implies not pseudo.";

    schemes_okay_to_request_in_any_process_.insert(scheme);
    schemes_okay_to_commit_in_any_process_.insert(scheme);
}

void ChildProcessSecurityPolicyImpl::RegisterWebSafeIsolatedScheme(
    const std::string& scheme,
    bool always_allow_in_origin_headers)
{
    base::AutoLock lock(lock_);
    DCHECK_EQ(0U, schemes_okay_to_request_in_any_process_.count(scheme))
        << "Add schemes at most once.";
    DCHECK_EQ(0U, pseudo_schemes_.count(scheme))
        << "Web-safe implies not pseudo.";

    schemes_okay_to_request_in_any_process_.insert(scheme);
    if (always_allow_in_origin_headers)
        schemes_okay_to_appear_as_origin_headers_.insert(scheme);
}

bool ChildProcessSecurityPolicyImpl::IsWebSafeScheme(
    const std::string& scheme)
{
    base::AutoLock lock(lock_);

    return base::ContainsKey(schemes_okay_to_request_in_any_process_, scheme);
}

void ChildProcessSecurityPolicyImpl::RegisterPseudoScheme(
    const std::string& scheme)
{
    base::AutoLock lock(lock_);
    DCHECK_EQ(0U, pseudo_schemes_.count(scheme)) << "Add schemes at most once.";
    DCHECK_EQ(0U, schemes_okay_to_request_in_any_process_.count(scheme))
        << "Pseudo implies not web-safe.";
    DCHECK_EQ(0U, schemes_okay_to_commit_in_any_process_.count(scheme))
        << "Pseudo implies not web-safe.";

    pseudo_schemes_.insert(scheme);
}

bool ChildProcessSecurityPolicyImpl::IsPseudoScheme(
    const std::string& scheme)
{
    base::AutoLock lock(lock_);

    return base::ContainsKey(pseudo_schemes_, scheme);
}

void ChildProcessSecurityPolicyImpl::GrantRequestURL(
    int child_id, const GURL& url)
{

    if (!url.is_valid())
        return; // Can't grant the capability to request invalid URLs.

    if (IsWebSafeScheme(url.scheme()))
        return; // The scheme has already been whitelisted for every child process.

    if (IsPseudoScheme(url.scheme())) {
        return; // Can't grant the capability to request pseudo schemes.
    }

    if (url.SchemeIsBlob() || url.SchemeIsFileSystem()) {
        return; // Don't grant blanket access to blob: or filesystem: schemes.
    }

    {
        base::AutoLock lock(lock_);
        SecurityStateMap::iterator state = security_state_.find(child_id);
        if (state == security_state_.end())
            return;

        // When the child process has been commanded to request this scheme,
        // we grant it the capability to request all URLs of that scheme.
        state->second->GrantScheme(url.scheme());
    }
}

void ChildProcessSecurityPolicyImpl::GrantRequestSpecificFileURL(
    int child_id,
    const GURL& url)
{
    if (!url.SchemeIs(url::kFileScheme))
        return;

    {
        base::AutoLock lock(lock_);
        SecurityStateMap::iterator state = security_state_.find(child_id);
        if (state == security_state_.end())
            return;

        // When the child process has been commanded to request a file:// URL,
        // then we grant it the capability for that URL only.
        base::FilePath path;
        if (net::FileURLToFilePath(url, &path))
            state->second->GrantRequestOfSpecificFile(path);
    }
}

void ChildProcessSecurityPolicyImpl::GrantReadFile(int child_id,
    const base::FilePath& file)
{
    GrantPermissionsForFile(child_id, file, READ_FILE_GRANT);
}

void ChildProcessSecurityPolicyImpl::GrantCreateReadWriteFile(
    int child_id, const base::FilePath& file)
{
    GrantPermissionsForFile(child_id, file, CREATE_READ_WRITE_FILE_GRANT);
}

void ChildProcessSecurityPolicyImpl::GrantCopyInto(int child_id,
    const base::FilePath& dir)
{
    GrantPermissionsForFile(child_id, dir, COPY_INTO_FILE_GRANT);
}

void ChildProcessSecurityPolicyImpl::GrantDeleteFrom(
    int child_id, const base::FilePath& dir)
{
    GrantPermissionsForFile(child_id, dir, DELETE_FILE_GRANT);
}

void ChildProcessSecurityPolicyImpl::GrantPermissionsForFile(
    int child_id, const base::FilePath& file, int permissions)
{
    base::AutoLock lock(lock_);

    SecurityStateMap::iterator state = security_state_.find(child_id);
    if (state == security_state_.end())
        return;

    state->second->GrantPermissionsForFile(file, permissions);
}

void ChildProcessSecurityPolicyImpl::RevokeAllPermissionsForFile(
    int child_id, const base::FilePath& file)
{
    base::AutoLock lock(lock_);

    SecurityStateMap::iterator state = security_state_.find(child_id);
    if (state == security_state_.end())
        return;

    state->second->RevokeAllPermissionsForFile(file);
}

void ChildProcessSecurityPolicyImpl::GrantReadFileSystem(
    int child_id, const std::string& filesystem_id)
{
    GrantPermissionsForFileSystem(child_id, filesystem_id, READ_FILE_GRANT);
}

void ChildProcessSecurityPolicyImpl::GrantWriteFileSystem(
    int child_id, const std::string& filesystem_id)
{
    GrantPermissionsForFileSystem(child_id, filesystem_id, WRITE_FILE_GRANT);
}

void ChildProcessSecurityPolicyImpl::GrantCreateFileForFileSystem(
    int child_id, const std::string& filesystem_id)
{
    GrantPermissionsForFileSystem(child_id, filesystem_id, CREATE_NEW_FILE_GRANT);
}

void ChildProcessSecurityPolicyImpl::GrantCreateReadWriteFileSystem(
    int child_id, const std::string& filesystem_id)
{
    GrantPermissionsForFileSystem(
        child_id, filesystem_id, CREATE_READ_WRITE_FILE_GRANT);
}

void ChildProcessSecurityPolicyImpl::GrantCopyIntoFileSystem(
    int child_id, const std::string& filesystem_id)
{
    GrantPermissionsForFileSystem(child_id, filesystem_id, COPY_INTO_FILE_GRANT);
}

void ChildProcessSecurityPolicyImpl::GrantDeleteFromFileSystem(
    int child_id, const std::string& filesystem_id)
{
    GrantPermissionsForFileSystem(child_id, filesystem_id, DELETE_FILE_GRANT);
}

void ChildProcessSecurityPolicyImpl::GrantSendMidiSysExMessage(int child_id)
{
    base::AutoLock lock(lock_);

    SecurityStateMap::iterator state = security_state_.find(child_id);
    if (state == security_state_.end())
        return;

    state->second->GrantPermissionForMidiSysEx();
}

void ChildProcessSecurityPolicyImpl::GrantOrigin(int child_id,
    const url::Origin& origin)
{
    base::AutoLock lock(lock_);

    SecurityStateMap::iterator state = security_state_.find(child_id);
    if (state == security_state_.end())
        return;

    state->second->GrantOrigin(origin);
}

void ChildProcessSecurityPolicyImpl::GrantScheme(int child_id,
    const std::string& scheme)
{
    base::AutoLock lock(lock_);

    SecurityStateMap::iterator state = security_state_.find(child_id);
    if (state == security_state_.end())
        return;

    state->second->GrantScheme(scheme);
}

void ChildProcessSecurityPolicyImpl::GrantWebUIBindings(int child_id)
{
    base::AutoLock lock(lock_);

    SecurityStateMap::iterator state = security_state_.find(child_id);
    if (state == security_state_.end())
        return;

    state->second->GrantBindings(BINDINGS_POLICY_WEB_UI);

    // Web UI bindings need the ability to request chrome: URLs.
    state->second->GrantScheme(kChromeUIScheme);

    // Web UI pages can contain links to file:// URLs.
    state->second->GrantScheme(url::kFileScheme);
}

void ChildProcessSecurityPolicyImpl::GrantReadRawCookies(int child_id)
{
    base::AutoLock lock(lock_);

    SecurityStateMap::iterator state = security_state_.find(child_id);
    if (state == security_state_.end())
        return;

    state->second->GrantReadRawCookies();
}

void ChildProcessSecurityPolicyImpl::RevokeReadRawCookies(int child_id)
{
    base::AutoLock lock(lock_);

    SecurityStateMap::iterator state = security_state_.find(child_id);
    if (state == security_state_.end())
        return;

    state->second->RevokeReadRawCookies();
}

bool ChildProcessSecurityPolicyImpl::CanRequestURL(
    int child_id, const GURL& url)
{
    if (!url.is_valid())
        return false; // Can't request invalid URLs.

    if (IsPseudoScheme(url.scheme())) {
        // Every child process can request <about:blank> and <about:srcdoc>.
        if (url == url::kAboutBlankURL || url == kAboutSrcDocURL)
            return true;
        // URLs like <about:version>, <about:crash>, <view-source:...> shouldn't be
        // requestable by any child process.  Also, this case covers
        // <javascript:...>, which should be handled internally by the process and
        // not kicked up to the browser.
        return false;
    }

    // Blob and filesystem URLs require special treatment, since they embed an
    // inner origin.
    if (url.SchemeIsBlob() || url.SchemeIsFileSystem()) {
        if (IsMalformedBlobUrl(url))
            return false;

        url::Origin origin(url);
        return origin.unique() || IsWebSafeScheme(origin.scheme()) || CanCommitURL(child_id, GURL(origin.Serialize()));
    }

    if (IsWebSafeScheme(url.scheme()))
        return true;

    // If the process can commit the URL, it can request it.
    if (CanCommitURL(child_id, url))
        return true;

    // Also allow URLs destined for ShellExecute and not the browser itself.
    return !GetContentClient()->browser()->IsHandledURL(url) && !net::URLRequest::IsHandledURL(url);
}

bool ChildProcessSecurityPolicyImpl::CanCommitURL(int child_id,
    const GURL& url)
{
    if (!url.is_valid())
        return false; // Can't commit invalid URLs.

    // Of all the pseudo schemes, only about:blank and about:srcdoc are allowed to
    // commit.
    if (IsPseudoScheme(url.scheme()))
        return url == url::kAboutBlankURL || url == kAboutSrcDocURL;

    // Blob and filesystem URLs require special treatment; validate the inner
    // origin they embed.
    if (url.SchemeIsBlob() || url.SchemeIsFileSystem()) {
        if (IsMalformedBlobUrl(url))
            return false;

        url::Origin origin(url);
        return origin.unique() || CanCommitURL(child_id, GURL(origin.Serialize()));
    }

    {
        base::AutoLock lock(lock_);

        // Most schemes can commit in any process. Note that we check
        // schemes_okay_to_commit_in_any_process_ here, which is stricter than
        // IsWebSafeScheme().
        //
        // TODO(creis, nick): https://crbug.com/515309: in generalized Site
        // Isolation and/or --site-per-process, there will be no such thing as a
        // scheme that is okay to commit in any process. Instead, an URL from a site
        // that is isolated may only be committed in a process dedicated to that
        // site, so CanCommitURL will need to rely on explicit, per-process grants.
        // Note how today, even with extension isolation, the line below does not
        // enforce that http pages cannot commit in an extension process.
        if (base::ContainsKey(schemes_okay_to_commit_in_any_process_, url.scheme()))
            return true;

        SecurityStateMap::iterator state = security_state_.find(child_id);
        if (state == security_state_.end())
            return false;

        // Otherwise, we consult the child process's security state to see if it is
        // allowed to commit the URL.
        return state->second->CanCommitURL(url);
    }
}

bool ChildProcessSecurityPolicyImpl::CanSetAsOriginHeader(int child_id,
    const GURL& url)
{
    if (!url.is_valid())
        return false; // Can't set invalid URLs as origin headers.

    // Suborigin URLs are a special case and are allowed to be an origin header.
    if (url.scheme() == url::kHttpSuboriginScheme || url.scheme() == url::kHttpsSuboriginScheme) {
        DCHECK(IsPseudoScheme(url.scheme()));
        return true;
    }

    // about:srcdoc cannot be used as an origin
    if (url == kAboutSrcDocURL)
        return false;

    // If this process can commit |url|, it can use |url| as an origin for
    // outbound requests.
    if (CanCommitURL(child_id, url))
        return true;

    // Allow schemes which may come from scripts executing in isolated worlds;
    // XHRs issued by such scripts reflect the script origin rather than the
    // document origin.
    {
        base::AutoLock lock(lock_);
        if (base::ContainsKey(schemes_okay_to_appear_as_origin_headers_,
                url.scheme()))
            return true;
    }
    return false;
}

bool ChildProcessSecurityPolicyImpl::CanReadFile(int child_id,
    const base::FilePath& file)
{
    return HasPermissionsForFile(child_id, file, READ_FILE_GRANT);
}

bool ChildProcessSecurityPolicyImpl::CanReadAllFiles(
    int child_id,
    const std::vector<base::FilePath>& files)
{
    return std::all_of(files.begin(), files.end(),
        [this, child_id](const base::FilePath& file) {
            return CanReadFile(child_id, file);
        });
}

bool ChildProcessSecurityPolicyImpl::CanCreateReadWriteFile(
    int child_id,
    const base::FilePath& file)
{
    return HasPermissionsForFile(child_id, file, CREATE_READ_WRITE_FILE_GRANT);
}

bool ChildProcessSecurityPolicyImpl::CanReadFileSystem(
    int child_id, const std::string& filesystem_id)
{
    return HasPermissionsForFileSystem(child_id, filesystem_id, READ_FILE_GRANT);
}

bool ChildProcessSecurityPolicyImpl::CanReadWriteFileSystem(
    int child_id, const std::string& filesystem_id)
{
    return HasPermissionsForFileSystem(child_id, filesystem_id,
        READ_FILE_GRANT | WRITE_FILE_GRANT);
}

bool ChildProcessSecurityPolicyImpl::CanCopyIntoFileSystem(
    int child_id, const std::string& filesystem_id)
{
    return HasPermissionsForFileSystem(child_id, filesystem_id,
        COPY_INTO_FILE_GRANT);
}

bool ChildProcessSecurityPolicyImpl::CanDeleteFromFileSystem(
    int child_id, const std::string& filesystem_id)
{
    return HasPermissionsForFileSystem(child_id, filesystem_id,
        DELETE_FILE_GRANT);
}

bool ChildProcessSecurityPolicyImpl::HasPermissionsForFile(
    int child_id, const base::FilePath& file, int permissions)
{
    base::AutoLock lock(lock_);
    bool result = ChildProcessHasPermissionsForFile(child_id, file, permissions);
    if (!result) {
        // If this is a worker thread that has no access to a given file,
        // let's check that its renderer process has access to that file instead.
        WorkerToMainProcessMap::iterator iter = worker_map_.find(child_id);
        if (iter != worker_map_.end() && iter->second != 0) {
            result = ChildProcessHasPermissionsForFile(iter->second,
                file,
                permissions);
        }
    }
    return result;
}

bool ChildProcessSecurityPolicyImpl::HasPermissionsForFileSystemFile(
    int child_id,
    const storage::FileSystemURL& filesystem_url,
    int permissions)
{
    if (!filesystem_url.is_valid())
        return false;

    // If |filesystem_url.origin()| is not committable in this process, then this
    // page should not be able to place content in that origin via the filesystem
    // API either.
    if (!CanCommitURL(child_id, filesystem_url.origin())) {
        UMA_HISTOGRAM_BOOLEAN("FileSystem.OriginFailedCanCommitURL", true);
        // TODO(nick): Temporary instrumentation for https://crbug.com/654479.
        base::debug::DumpWithoutCrashing();
        return false;
    }

    if (filesystem_url.path().ReferencesParent())
        return false;

    // Any write access is disallowed on the root path.
    if (storage::VirtualPath::IsRootPath(filesystem_url.path()) && (permissions & ~READ_FILE_GRANT)) {
        return false;
    }

    if (filesystem_url.mount_type() == storage::kFileSystemTypeIsolated) {
        // When Isolated filesystems is overlayed on top of another filesystem,
        // its per-filesystem permission overrides the underlying filesystem
        // permissions).
        return HasPermissionsForFileSystem(
            child_id, filesystem_url.mount_filesystem_id(), permissions);
    }

    FileSystemPermissionPolicyMap::iterator found = file_system_policy_map_.find(filesystem_url.type());
    if (found == file_system_policy_map_.end())
        return false;

    if ((found->second & storage::FILE_PERMISSION_READ_ONLY) && permissions & ~READ_FILE_GRANT) {
        return false;
    }

    if (found->second & storage::FILE_PERMISSION_USE_FILE_PERMISSION)
        return HasPermissionsForFile(child_id, filesystem_url.path(), permissions);

    if (found->second & storage::FILE_PERMISSION_SANDBOX)
        return true;

    return false;
}

bool ChildProcessSecurityPolicyImpl::CanReadFileSystemFile(
    int child_id,
    const storage::FileSystemURL& filesystem_url)
{
    return HasPermissionsForFileSystemFile(child_id, filesystem_url,
        READ_FILE_GRANT);
}

bool ChildProcessSecurityPolicyImpl::CanWriteFileSystemFile(
    int child_id,
    const storage::FileSystemURL& filesystem_url)
{
    return HasPermissionsForFileSystemFile(child_id, filesystem_url,
        WRITE_FILE_GRANT);
}

bool ChildProcessSecurityPolicyImpl::CanCreateFileSystemFile(
    int child_id,
    const storage::FileSystemURL& filesystem_url)
{
    return HasPermissionsForFileSystemFile(child_id, filesystem_url,
        CREATE_NEW_FILE_GRANT);
}

bool ChildProcessSecurityPolicyImpl::CanCreateReadWriteFileSystemFile(
    int child_id,
    const storage::FileSystemURL& filesystem_url)
{
    return HasPermissionsForFileSystemFile(child_id, filesystem_url,
        CREATE_READ_WRITE_FILE_GRANT);
}

bool ChildProcessSecurityPolicyImpl::CanCopyIntoFileSystemFile(
    int child_id,
    const storage::FileSystemURL& filesystem_url)
{
    return HasPermissionsForFileSystemFile(child_id, filesystem_url,
        COPY_INTO_FILE_GRANT);
}

bool ChildProcessSecurityPolicyImpl::CanDeleteFileSystemFile(
    int child_id,
    const storage::FileSystemURL& filesystem_url)
{
    return HasPermissionsForFileSystemFile(child_id, filesystem_url,
        DELETE_FILE_GRANT);
}

bool ChildProcessSecurityPolicyImpl::HasWebUIBindings(int child_id)
{
    base::AutoLock lock(lock_);

    SecurityStateMap::iterator state = security_state_.find(child_id);
    if (state == security_state_.end())
        return false;

    return state->second->has_web_ui_bindings();
}

bool ChildProcessSecurityPolicyImpl::CanReadRawCookies(int child_id)
{
    base::AutoLock lock(lock_);

    SecurityStateMap::iterator state = security_state_.find(child_id);
    if (state == security_state_.end())
        return false;

    return state->second->can_read_raw_cookies();
}

void ChildProcessSecurityPolicyImpl::AddChild(int child_id)
{
    if (security_state_.count(child_id) != 0) {
        NOTREACHED() << "Add child process at most once.";
        return;
    }

    security_state_[child_id] = base::MakeUnique<SecurityState>();
}

bool ChildProcessSecurityPolicyImpl::ChildProcessHasPermissionsForFile(
    int child_id, const base::FilePath& file, int permissions)
{
    SecurityStateMap::iterator state = security_state_.find(child_id);
    if (state == security_state_.end())
        return false;
    return state->second->HasPermissionsForFile(file, permissions);
}

bool ChildProcessSecurityPolicyImpl::CanAccessDataForOrigin(int child_id,
    const GURL& gurl)
{
    base::AutoLock lock(lock_);
    SecurityStateMap::iterator state = security_state_.find(child_id);
    if (state == security_state_.end()) {
        // TODO(nick): Returning true instead of false here is a temporary
        // workaround for https://crbug.com/600441
        return true;
    }
    return state->second->CanAccessDataForOrigin(gurl);
}

bool ChildProcessSecurityPolicyImpl::HasSpecificPermissionForOrigin(
    int child_id,
    const url::Origin& origin)
{
    base::AutoLock lock(lock_);
    SecurityStateMap::iterator state = security_state_.find(child_id);
    if (state == security_state_.end())
        return false;
    return state->second->CanCommitOrigin(origin);
}

void ChildProcessSecurityPolicyImpl::LockToOrigin(int child_id,
    const GURL& gurl)
{
    // "gurl" can be currently empty in some cases, such as file://blah.
    DCHECK(SiteInstanceImpl::GetSiteForURL(NULL, gurl) == gurl);
    base::AutoLock lock(lock_);
    SecurityStateMap::iterator state = security_state_.find(child_id);
    DCHECK(state != security_state_.end());
    state->second->LockToOrigin(gurl);
}

void ChildProcessSecurityPolicyImpl::GrantPermissionsForFileSystem(
    int child_id,
    const std::string& filesystem_id,
    int permission)
{
    base::AutoLock lock(lock_);

    SecurityStateMap::iterator state = security_state_.find(child_id);
    if (state == security_state_.end())
        return;
    state->second->GrantPermissionsForFileSystem(filesystem_id, permission);
}

bool ChildProcessSecurityPolicyImpl::HasPermissionsForFileSystem(
    int child_id,
    const std::string& filesystem_id,
    int permission)
{
    base::AutoLock lock(lock_);

    SecurityStateMap::iterator state = security_state_.find(child_id);
    if (state == security_state_.end())
        return false;
    return state->second->HasPermissionsForFileSystem(filesystem_id, permission);
}

void ChildProcessSecurityPolicyImpl::RegisterFileSystemPermissionPolicy(
    storage::FileSystemType type,
    int policy)
{
    base::AutoLock lock(lock_);
    file_system_policy_map_[type] = policy;
}

bool ChildProcessSecurityPolicyImpl::CanSendMidiSysExMessage(int child_id)
{
    base::AutoLock lock(lock_);

    SecurityStateMap::iterator state = security_state_.find(child_id);
    if (state == security_state_.end())
        return false;

    return state->second->can_send_midi_sysex();
}

} // namespace content
